package com.hsf.cloudweb.utils;

import org.springframework.util.StringUtils;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class DynamicGenClassUtil {
    /**java动态创建class的两种方式之一：写入文件
     * className.java文件在packageName包下,生成的class文件在对应项目根目录下的target/class目录下
     * 重点：1-编译class; 2-加载class文件
     * @param subDir 当前项目根目录下子项目目录，针对聚合项目下的子项目目录
     * @param importClasses 需要导入的带包名的类集合
     * @param fieldNameTypeMap  //属性名-类型Map
     * @param packageName       //包名
     * @param className        //类名
     * @param methodBody   //非set，get方法体字符串
     * @throws IOException,ClassNotFoundException,InstantiationException,IllegalAccessException
     * @return Object instance
     */
    public static Object genDynamicClassInstance(String subDir,List<String> importClasses,Map<String,String> fieldNameTypeMap, String packageName, String className,String methodBody) throws IOException,ClassNotFoundException,InstantiationException,IllegalAccessException {
        checkArgs(packageName,className);
        String workDir = System.getProperty("user.dir")+"\\"+subDir;
        String dirPath = workDir+"\\src\\main\\java\\";
        System.out.println("dirPath:"+dirPath);
        //"."前必须加转义字符，否则无法准换成目录
        String packagePath = packageName.replaceAll("\\.","\\\\");
        String filename =dirPath+packagePath+"\\"+className+".java";
        System.out.println("filename:"+filename);
        File file = new File(filename);
        if(!file.exists()){ //如果当前目录不存在，则创建目录
//            file.mkdir();  由于目录已存在，无需再创建目录
            file.createNewFile();
        }
        FileWriter fileWriter = new FileWriter(file);
        String classString = genClassString(importClasses,fieldNameTypeMap,packageName,className,methodBody);
        fileWriter.write(classString);
        fileWriter.flush();
        fileWriter.close();

        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager manager = compiler.getStandardFileManager(null,null,null);
        Iterable<?extends JavaFileObject> javaFileObjects = manager.getJavaFileObjects(file);
        String dest = workDir+"\\target\\classes"; //注意目录分隔符之间必须使用\转义字符
        //options就是指定编译输入目录，与我们命令行写javac -d C://是一样的
        List<String> options = new ArrayList<>();
        options.add("-d");
        options.add(dest);
        JavaCompiler.CompilationTask task = compiler.getTask(null,manager,null,options,null,javaFileObjects);
        task.call();
        manager.close();
        URL url = new URL("file:/"+dest);
        URL[] urls = new URL[]{url};
        //加载class时要告诉你的classloader去哪个位置加载class文件
        ClassLoader classLoader = new URLClassLoader(urls);
        Object instance = classLoader.loadClass(packageName+"."+className).newInstance();
        return instance;

    }

    public static String genClassString(List<String> importClasses,Map<String,String> fieldNameTypeMap,String packageName, String className, String methodBody){
        StringBuilder builder = new StringBuilder("package\t"+packageName+";\n"); //每一行代码后面注意分号和换行符的使用
        builder.append("import java.io.Serializable;\n");
        if(importClasses.size()>0){
            for(String importClass: importClasses){
                builder.append("import ").append(importClass).append(";\n");
            }
        }

        //在多线程中为了线程安全需要将StringBuilder换为StringBuffer
        builder.append("public class "+className+"\timplements Serializable\t{\n");
        for(Map.Entry<String,String> entry: fieldNameTypeMap.entrySet()){
                builder.append("\n\t private "+entry.getValue()+"\t"+entry.getKey()+";\n");
        }
        for(Map.Entry<String,String> entry: fieldNameTypeMap.entrySet()){
            String methodNameSuffix = upperCaseFirstChar(entry.getKey());
            builder.append("\n\t public void set"+methodNameSuffix+'('+entry.getValue()+' '+entry.getKey()+"){\n");
            builder.append("\n\t    this."+entry.getKey()+" = "+entry.getKey()+";\n\t }\n");
            builder.append("\n\t public "+entry.getValue()+ "  get"+methodNameSuffix+"(){\n");
            builder.append("\n\t     return this."+entry.getKey()+";\n\t }\n");
        }


        if(!StringUtils.isEmpty(methodBody)){
            builder.append(methodBody); //方法体字符窜必须带完整的{},如果需要加入多个方法体，则可将methodBody的参数类型换为List<String>
        }
        builder.append("\n}");
        return builder.toString();
    }


    public static String upperCaseFirstChar(String fieldName){
        if(fieldName==null || fieldName.length()==0){
            return null;
        }
        char[] chs = fieldName.toCharArray();
        if(chs[0]>='a' && chs[0]<='z'){
            chs[0] = (char) (chs[0]-32);
        }
        return new String(chs);
    }

    public static void checkArgs(String packageName,String className){
        if(StringUtils.isEmpty(packageName) || StringUtils.isEmpty(className)){
            throw new IllegalArgumentException("parameter packageName or className cannot be empty!");
        }
    }

    /**通过反射填充属性值
     *
     * @param fieldMap  属性名-值 键值对
     * @param instance  反射生成的类实例
     */
    public static void fillFields(Map<String,Object> fieldMap,Object instance)throws NoSuchFieldException,NoSuchMethodException,IllegalAccessException, InvocationTargetException {
        Method method;
        Field field;
        Class clazz = instance.getClass();
        Class fieldClass;
        for(Map.Entry<String,Object> entry: fieldMap.entrySet()){
            field = clazz.getDeclaredField(entry.getKey());
            fieldClass = field.getType();
            method = clazz.getDeclaredMethod("set"+upperCaseFirstChar(entry.getKey()),fieldClass);
            method.invoke(instance,entry.getValue());
        }

    }

}
